﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using App.Core.Extensions;
using HtmlAgilityPack;
using WebScrapper.Entities;
using CarlosAg.ExcelXmlWriter;
using System.IO;
using System.Text.RegularExpressions;

namespace WebScrapper.Implementations
{
    public class CymaxWeb : BaseImplementation<WebScrapper.Entities.Product>
    {
        ///Sample Input URL Pattern = "Outdoor-Lighting--C904--{0}.htm"
        private const String DOMAIN = "http://www.cymaxstores.com/NoBot/";
        private String _Model = String.Empty;

        private String URL { get; set; }
        
        /// <summary>
        /// Initializes the Implementation object of CymaxWeb with the Associated Form
        /// </summary>
        /// <param name="form"></param>
        public CymaxWeb(RichTextBox txtStatus) : base(txtStatus)
        {
            
        }
        /// <summary>
        /// Starts Processing of a Category.
        /// </summary>
        /// <param name="urlPart">
        /// <para>The URL Part with Category. It Should be given in the followig format:</para>
        /// <para>[ Outdoor-Lighting--C904--{0}.htm ] --> Very Very Important</para>
        /// </param>
        public override void StartScrapping(String urlPart)
        {
            ///Extract the Model First from Supplied Mast Page URL Part
            String[] parts = Regex.Split(urlPart.Split('.')[0], "--");
            if (parts != null && parts.Length > 0)
                _Model = parts[parts.Length - 2];

            this.URL = DOMAIN + urlPart;
            //this.Form.Cursor = Cursors.WaitCursor;
            //Form.btnStart.Enabled = false;
            //Read the Excell Spread Sheet with Categories and foreach category loop following process.
            //Start Processing a Category
            UpdateStatusText(String.Format("Started Processing of Category/URL: {0}", this.URL));
            ProcessMasterListPage();
            //this.Form.Cursor = Cursors.WaitCursor;
            //this.Form.btnStart.Enabled = true;
        }

        private void ProcessMasterListPage()
        {
            List<Product> extractedProducts = new List<Product>();
            for (int i = 1; i < int.MaxValue; i++)
            {
                String url = String.Format(this.URL, i);
                base.UpdateStatusText(String.Format("Requesting Master List Page: {0}", url));
                HtmlWeb hw = new HtmlWeb();
                HtmlAgilityPack.HtmlDocument doc = hw.Load(url);
                if (doc != null)
                {
                    HtmlNodeCollection productNodes = doc.DocumentNode.SelectNodes("//div[@id='content-cell']");
                    if (productNodes == null || productNodes.Count == 0)
                        break; ///There is no more page to process in this category
                    else
                    {
                        ///Inside a List Page with Products
                        ///Now process each product div to extract data
                        foreach (HtmlNode productNode in productNodes)
                        {
                            ///Get Data With Div Index
                            HtmlNodeCollection divs = productNode.SelectNodes("div");
                            Product product = new Product();
                            product.Name = base.CleanUpText(divs[1].InnerText);
                            product.Model = _Model;
                            HtmlNode detailsAnchor = divs[1].SelectNodes("a")[0];
                            product.DetailsUrl = String.Format("{0}{1}", DOMAIN, detailsAnchor.Attributes["href"].Value);
                            product.FreeShipping = divs[2].InnerText.IndexOf("Free Shipping") > -1 ? "Yes" : "No";
                            product.Price = base.CleanUpText(divs[3].SelectSingleNode("div").InnerText).Replace("$", String.Empty);
                            GetProductDetails(product);
                            extractedProducts.Add(product);
                            if (extractedProducts.Count > 2)
                                break;
                        }
                        ///TODO: For Testing
                        break;
                    }                    
                }
            }
            String fileName = OutputFile(extractedProducts, true, String.Format("{0}.xls", _Model));
            UpdateStatusText(String.Format("'{0}{1}' Has been created successfully.", Environment.NewLine, fileName));
        }

        private void GetProductDetails(Product product)
        {
            if (product.DetailsUrl.IsNullOrEmpty())
                UpdateStatusText(String.Format("Product details URL can't be extracted for: {0}", product.Name));
            else
            {
                HtmlWeb hw = new HtmlWeb();
                UpdateStatusText(String.Format("Loading product details from: {0}", product.DetailsUrl));
                HtmlAgilityPack.HtmlDocument doc = hw.Load(product.DetailsUrl);
                if (doc == null)
                    UpdateStatusText("Unable to Get product details for above URL.");
                else
                {
                    HtmlNode detailsBox = doc.DocumentNode.SelectSingleNode("//div[@class='details-box']");
                    if (detailsBox == null)
                        UpdateStatusText("The details box was not found. So, details not loaded");
                    else
                    {
                        String xmlFileName = SaveAsXML(detailsBox, product.DetailsUrl);
                        ///Remove the Unnecessary Node for Scrapping
                        HtmlNode previewNode = detailsBox.SelectSingleNode("//div[@id='prReviewEngineDiv']");
                        if(previewNode != null)
                            detailsBox.RemoveChild(previewNode);

                        HtmlNodeCollection ulNodes = detailsBox.SelectNodes("ul");
                        
                        if (ulNodes != null && ulNodes.Count > 0)
                        {
                            foreach (HtmlNode ul in ulNodes)
                            {                                
                                GetProductFeaturesOrSpecifications(product, ul);
                            }
                            //IEnumerable<HtmlNode> ancestors = ulNodes[0].Ancestors();
                            //IEnumerable<HtmlNode> descendents = ulNodes[0].Descendants();
                        }
                    }
                }
            }
        }

        private List<String> extractLiElementsContent(HtmlNode ul)
        {
            String[] lines = Regex.Split(ul.InnerHtml.Replace("</li>", String.Empty), "<li>");
            List<String> liContents = new List<String>();
            foreach (String str in lines)
                if (!str.Trim().IsNullOrEmpty())
                    liContents.Add(CleanUpText(str));

            return liContents;
        }

        private string SaveAsXML(HtmlNode detailsBox, String detailsUrl)
        {
            String[] parts = detailsUrl.Split('-');
            String fileName = parts[parts.Length - 1];
            fileName = String.Format("{0}.xml", fileName.Replace(".htm", String.Empty).Replace(".html", String.Empty));
            String filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CymaxWeb Product Details");
            if (!Directory.Exists(filePath))
                Directory.CreateDirectory(filePath);

            HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
            doc.LoadHtml(detailsBox.InnerHtml);
            doc.OptionOutputAsXml = true;
            fileName = Path.Combine(filePath, fileName);
            doc.Save(fileName);
            return fileName;
        }

        private void GetProductFeaturesOrSpecifications(Product product, HtmlNode ul)
        {
            List<String> liContents = extractLiElementsContent(ul);
            //HtmlNodeCollection lis = ul.SelectNodes("li");
            if (liContents != null && liContents.Count > 0)
            {
                String firstLi = liContents[0];
                if (IsProductSpecification(firstLi))
                    extractSpecification(product, liContents);
                else
                    extractFeatures(product, liContents);
            }
        }
        private void extractSpecification(Product product, List<String> lis)
        {
            String dimension = lis[0].Replace("Product dimensions:", String.Empty);
            dimension = dimension.Trim().StripExtraSpace();
            product.Dimensions = dimension;
            String[] dimensionParts = dimension.Split('x');
            if (dimensionParts != null && dimensionParts.Length > 0)
            {
                foreach (String str in dimensionParts)
                {
                    if (str.Trim().EndsWith("H"))
                        product.Height = str.Replace("H", String.Empty).Replace("\"", String.Empty);
                    else if (str.Trim().EndsWith("D"))
                        product.Diameter = str.Replace("D", String.Empty).Replace("\"", String.Empty);
                    else if (str.Trim().EndsWith("W"))
                        product.Width = str.Replace("W", String.Empty).Replace("\"", String.Empty);
                }
            }
        }

        private void extractFeatures(Product product, List<String> liContents)
        {
            product.Features = String.Empty;
            foreach (String li in liContents)
            {
                product.Features += String.Format("{0}{1}", CleanUpText(li), Environment.NewLine);
            }
        }

        private bool IsProductSpecification(String str)
        {
            if (CleanUpText(str).IndexOf("Product dimensions:") > -1)
                return true;

            return false;
        }
        /// <summary>
        /// Creates the Output File from Extracted Data
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="addColumnHeader"></param>
        /// <param name="outputFileName"></param>
        /// <returns></returns>
        public override String OutputFile(List<WebScrapper.Entities.Product> products, bool addColumnHeader, string outputFileName)
        {            
            return ToExcel(products, addColumnHeader, outputFileName);
        }

        private String ToExcel(List<Product> products, bool addColumnHeader, String outputFileName)
        {
            Workbook book = new Workbook();
            Worksheet sheet = book.Worksheets.Add("Sheet1");
            if (products != null && products.Count > 0)
            {
                if (addColumnHeader)
                {
                    WorksheetRow headerRow = sheet.Table.Rows.Add();
                    addHeaderRow(headerRow);
                }

                for (int i = 0; i < products.Count; i++)
                {
                    WorksheetRow row = sheet.Table.Rows.Add();
                    Product product = products[i];
                    addDataRow(row, product);
                }
            }

            //row.Cells.Add("Hello World");        

            //String fileName = String.Format("{0}.xls", System.Guid.NewGuid().ToString());
            String filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "OutputExcel"); //HttpContext.Current.Server.MapPath(AppConstants.TEMP_DIRECTORY);            
            if (!System.IO.Directory.Exists(filePath))
                System.IO.Directory.CreateDirectory(filePath);

            filePath = System.IO.Path.Combine(filePath, outputFileName);

            book.Save(filePath);

            return filePath;
        }
        private void addHeaderRow(WorksheetRow row)
        {           
            row.Cells.Add("Name");
            row.Cells.Add("Model");
            row.Cells.Add("Price");
            row.Cells.Add("Free Shipping");
            row.Cells.Add("Width");
            row.Cells.Add("Height");
            row.Cells.Add("Diameter");
            row.Cells.Add("Details");
            row.Cells.Add("Details Url");
        }
        private void addDataRow(WorksheetRow row, Product product)
        {            
            row.Cells.Add(product.Name);
            row.Cells.Add(product.Model);
            row.Cells.Add(product.Price);
            row.Cells.Add(product.FreeShipping);
            row.Cells.Add(product.Width);
            row.Cells.Add(product.Height);
            row.Cells.Add(product.Diameter);
            row.Cells.Add(product.Details);
            row.Cells.Add(product.DetailsUrl);
        }
    }
}
